/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.unimod.fsmleditor;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ResourceBundle;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.jface.text.source.projection.ProjectionSupport;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.TextEditorAction;
import org.eclipse.ui.texteditor.TextOperationAction;

import com.evelopers.unimod.plugin.eclipse.transform.gxml.GModelToGXML;
import com.evelopers.unimod.plugin.eclipse.ui.UniModEditor;
import com.evelopers.unimod.plugin.eclipse.ui.UniModPerspective;
import com.evelopers.unimod.transform.TransformException;
import com.unimod.fsml.model.FSMLModelHelper;
import com.unimod.fsml.validation.Validator;
import com.unimod.fsmleditor.configuration.FSMLSourceViewerConfiguration;

/**
 * FSML specific text editor.
 */
public class FSMLEditor extends TextEditor {
    private static final Log log = LogFactory.getLog(FSMLEditor.class);
    
    /** The ID of this editor as defined in plugin.xml */
    public static final String EDITOR_ID = "com.unimod.fsmleditor.FSMLEditor";

    /** The ID of the editor context menu */
    public static final String EDITOR_CONTEXT = EDITOR_ID + ".context";

    /** The ID of the editor ruler context menu */
    public static final String RULER_CONTEXT = EDITOR_CONTEXT + ".ruler";

    private static final String UNIMOD_EDITOR_ID = "com.evelopers.unimod.plugin.eclipse.ui.UniModEditor";
    
	private class DefineFoldingRegionAction extends TextEditorAction {

		public DefineFoldingRegionAction(ResourceBundle bundle, String prefix, ITextEditor editor) {
			super(bundle, prefix, editor);
		}
		
		private IAnnotationModel getAnnotationModel(ITextEditor editor) {
			return (IAnnotationModel) editor.getAdapter(ProjectionAnnotationModel.class);
		}
		
		/*
		 * @see org.eclipse.jface.action.Action#run()
		 */
		@Override
        public void run() {
			ITextEditor editor = getTextEditor();
			ISelection selection = editor.getSelectionProvider().getSelection();
			if (selection instanceof ITextSelection) {
				ITextSelection textSelection = (ITextSelection) selection;
				if (!textSelection.isEmpty()) {
					IAnnotationModel model = getAnnotationModel(editor);
					if (model != null) {
						
						int start = textSelection.getStartLine();
						int end = textSelection.getEndLine();
						
						try {
							IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput());
							int offset = document.getLineOffset(start);
							int endOffset = document.getLineOffset(end + 1);
							Position position = new Position(offset, endOffset - offset);
							model.addAnnotation(new ProjectionAnnotation(), position);
						} catch (BadLocationException x) {
							// ignore
						}
					}
				}
			}
		}
	}

	/** The outline page */
	private FSMLContentOutlinePage fOutlinePage;
	/** The projection support */
	private ProjectionSupport fProjectionSupport;
	/** FSML program validator */
	private Validator validator;
	/** FSML model container */
	private FSMLModelHelper modelHelper;
	/** UniMod editor instance to use its validator */
//	private UniModEditor uniModEditor;

	/**
	 * Default constructor.
	 */
	public FSMLEditor() {
		super();
		
		modelHelper = FSMLModelHelper.getInstance();
	}
	
	/** The {@link FSMLEditor FSML editor} implementation of this 
	 * {@linkplain AbstractTextEditor} method extend the 
	 * actions to add those specific to the receiver
	 */
	@Override
    protected void createActions() {
		super.createActions();
		
		IAction a = new TextOperationAction(FSMLEditorMessages.getResourceBundle(), "ContentAssistProposal.", this, ISourceViewer.CONTENTASSIST_PROPOSALS);
		a.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
		setAction("ContentAssistProposal", a);
		
		a = new TextOperationAction(FSMLEditorMessages.getResourceBundle(), "ContentAssistTip.", this, ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION);
		a.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_CONTEXT_INFORMATION);
		setAction("ContentAssistTip", a);
		
//		a = new DefineFoldingRegionAction(FSMLEditorMessages.getResourceBundle(), "DefineFoldingRegion.", this);
//		setAction("DefineFoldingRegion", a);
	}
	
    public void init(IEditorSite site, IEditorInput input) throws PartInitException {
        super.init(site, input);

        // start validator
        validator = new Validator(this);
        validator.forceValidation();
        
//      runUniModValidator();

        // init breakpoint manager
//        breakpointManager = new BreakpointManager(this);
//        breakpointManager.init();
    }
    
    /** The {@link FSMLEditor FSML editor} implementation of this 
	 * {@linkplain AbstractTextEditor} method performs any extra 
	 * disposal actions required by the FSML editor.
	 */
	@Override
    public void dispose() {
//		if (fOutlinePage != null)
//			fOutlinePage.setInput(null);
		super.dispose();
		validator.dispose();
	}
	
	/** The {@link FSMLEditor FSML editor} implementation of this 
	 * {@linkplain AbstractTextEditor} method performs any extra 
	 * revert behavior required by the FSML editor.
	 */
	@Override
    public void doRevertToSaved() {
		super.doRevertToSaved();
//		if (fOutlinePage != null)
//			fOutlinePage.update();
	}
	
	/** The {@link FSMLEditor FSML editor} implementation of this 
	 * {@linkplain AbstractTextEditor} method performs any extra 
	 * save behavior required by the FSML editor.
	 * 
	 * @param monitor the progress monitor
	 */
	@Override
    public void doSave(IProgressMonitor monitor) {
		super.doSave(monitor);
//		if (fOutlinePage != null)
//			fOutlinePage.update();

/*
		try {
	        String fileName = "A1.unimod";
	        IPath path = new Path(fileName);
	        IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
            if (file.exists()
                    || MessageDialogWithToggle
                            .openConfirm(
                                    getSite().getShell(),
                                    "Create File",
                                    "The file '"
                                            + file.getName()
                                            + "' doesn't exist. Click OK to create it.")) {

                saveModelInXml(modelHelper.getProgramModel(), file, monitor);
            }
        } catch (CoreException e) {
            ErrorDialog.openError(getSite().getShell(), "Error During Save",
                    "The current workflow model could not be saved.", e
                            .getStatus());
        }
*/	}
	
    /** The {@link FSMLEditor FSML editor} implementation of this 
	 * {@linkplain AbstractTextEditor} method performs any extra 
	 * save as behavior required by the FSML editor.
	 */
	@Override
    public void doSaveAs() {
		super.doSaveAs();
//		if (fOutlinePage != null)
//			fOutlinePage.update();

		SaveAsDialog dialog = new SaveAsDialog(getSite().getShell());
		IFile originalFile = ((IFileEditorInput) getEditorInput()).getFile();
        dialog.setOriginalFile(originalFile);
        dialog.open();
        IPath path = dialog.getResult();

        if (path == null) {
            return;
        }

        ProgressMonitorDialog progressMonitorDialog = new ProgressMonitorDialog(getSite().getShell());
        IProgressMonitor progressMonitor = progressMonitorDialog.getProgressMonitor();

        IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
		try {
			file.setContents(originalFile.getContents(), true, true, progressMonitor);
		} catch (CoreException e) {
            ErrorDialog.openError(getSite().getShell(), "Error During Save",
                    "The current workflow model could not be saved.", e.getStatus());
		}
	}
	
	/** The {@link FSMLEditor FSML editor} implementation of this 
	 * {@linkplain AbstractTextEditor} method performs sets the 
	 * input of the outline page after AbstractTextEditor has set input.
	 * 
	 * @param input the editor input
	 * @throws CoreException in case the input can not be set
	 */ 
	@Override
    public void doSetInput(IEditorInput input) throws CoreException {
		super.doSetInput(input);
//		if (fOutlinePage != null)
//			fOutlinePage.setInput(input);
	}
	
	/*
	 * @see org.eclipse.ui.texteditor.ExtendedTextEditor#editorContextMenuAboutToShow(org.eclipse.jface.action.IMenuManager)
	 */
	@Override
    protected void editorContextMenuAboutToShow(IMenuManager menu) {
		super.editorContextMenuAboutToShow(menu);
		addAction(menu, "ContentAssistProposal");
		addAction(menu, "ContentAssistTip");
//		addAction(menu, "DefineFoldingRegion");
	}
	
	/** The {@link FSMLEditor FSML editor} implementation of this 
	 * {@linkplain AbstractTextEditor} method performs gets
	 * the java content outline page if request is for a an 
	 * outline page.
	 * 
	 * @param required the required type
	 * @return an adapter for the required type or <code>null</code>
	 */ 
	@SuppressWarnings("unchecked")
    @Override
    public Object getAdapter(Class required) {
//		if (IContentOutlinePage.class.equals(required)) {
//			if (fOutlinePage == null) {
//				fOutlinePage = new FSMLContentOutlinePage(getDocumentProvider(), this);
//				if (getEditorInput() != null)
//					fOutlinePage.setInput(getEditorInput());
//			}
//			return fOutlinePage;
//		}
		
		if (fProjectionSupport != null) {
			Object adapter = fProjectionSupport.getAdapter(getSourceViewer(), required);
			if (adapter != null)
				return adapter;
		}
		
		return super.getAdapter(required);
	}
		
	/* (non-Javadoc)
	 * Method declared on AbstractTextEditor
	 */
	@Override
    protected void initializeEditor() {
		super.initializeEditor();
        setEditorContextMenuId(EDITOR_CONTEXT);
        setRulerContextMenuId(RULER_CONTEXT);
		setSourceViewerConfiguration(new FSMLSourceViewerConfiguration());
	}

	/*
	 * @see org.eclipse.ui.texteditor.ExtendedTextEditor#createSourceViewer(org.eclipse.swt.widgets.Composite, org.eclipse.jface.text.source.IVerticalRuler, int)
	 */
	@Override
    protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
		
		fAnnotationAccess = createAnnotationAccess();
		fOverviewRuler = createOverviewRuler(getSharedColors());
		
		ISourceViewer viewer = new ProjectionViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), styles);
		// ensure decoration support has been created and configured.
		getSourceViewerDecorationSupport(viewer);
		
		return viewer;
	}
	
	/*
	 * @see org.eclipse.ui.texteditor.ExtendedTextEditor#createPartControl(org.eclipse.swt.widgets.Composite)
	 */
	@Override
    public void createPartControl(Composite parent) {
		super.createPartControl(parent);
		ProjectionViewer viewer = (ProjectionViewer) getSourceViewer();
		fProjectionSupport = new ProjectionSupport(viewer, getAnnotationAccess(), getSharedColors());
		fProjectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.error");
		fProjectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.warning");
		fProjectionSupport.install();
		viewer.doOperation(ProjectionViewer.TOGGLE);
	}
	
	/*
	 * @see org.eclipse.ui.texteditor.AbstractTextEditor#adjustHighlightRange(int, int)
	 */
	@Override
    protected void adjustHighlightRange(int offset, int length) {
		ISourceViewer viewer = getSourceViewer();
		if (viewer instanceof ITextViewerExtension5) {
			ITextViewerExtension5 extension= (ITextViewerExtension5) viewer;
			extension.exposeModelRange(new Region(offset, length));
		}
	}

	private void runUniModValidator() {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
			GModelToGXML.write(modelHelper.getProgramModel(), out);
		} catch (TransformException e) {
			log.error("Cannot write UniMod model to xml", e);
		}
        InputStream initialContent = new ByteArrayInputStream(out.toByteArray());

        String fileName = "A1.unimod";
        IFile file = ResourcesPlugin.getWorkspace().getRoot().getProject().getFile(fileName);
        try {
			file.create(initialContent, true, null);
		} catch (CoreException e) {
			log.error(String.format("Cannot create file '%s' in workspace", fileName), e);
		}
        
        try {
			UniModEditor uniModEditor = (UniModEditor) IDE.openEditor(this.getSite().getPage(), file, false);
			uniModEditor.getValidator().forceValidation();
		} catch (PartInitException e) {
			log.error("Cannot open file in UniMod editor", e);
		}
	}
	
	private void openUniModEditor(IEditorInput input) {
		try {
			IWorkbenchPage page = this.getSite().getWorkbenchWindow().openPage(UniModPerspective.ID, input);
			UniModEditor uniModEditor = (UniModEditor) IDE.openEditor(page, input, UNIMOD_EDITOR_ID);
			uniModEditor.init(this.getEditorSite(), input);
//			IWorkbench workbench = this.getSite().getPage().getWorkbenchWindow().getWorkbench();
//			IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
//			IWorkbenchPage page = window.getActivePage();
//			IDE.openEditor(page, input, UNIMOD_EDITOR_ID);
//          workbench.showPerspective(UniModPerspective.ID, window);
		} catch (WorkbenchException e) {
			log.warn("Cannot create a workbench for UniMod editor", e);
		}
	}
}
